package com.hongshu.web.service.web.impl;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hongshu.common.core.constant.NoteConstant;
import com.hongshu.common.core.domain.NoteSearchVo;
import com.hongshu.common.core.enums.AuditStatusEnum;
import com.hongshu.common.core.exception.HongshuException;
import com.hongshu.common.core.utils.DozerUtil;
import com.hongshu.common.core.utils.RedisUtils;
import com.hongshu.common.core.utils.bean.BeanUtils;
import com.hongshu.common.core.utils.ip.AddressUtils;
import com.hongshu.common.core.utils.ip.IpUtils;
import com.hongshu.web.auth.AuthContextHolder;
import com.hongshu.web.domain.NearbyNotesPage;
import com.hongshu.web.domain.dto.EsNoteDTO;
import com.hongshu.web.domain.dto.SystemConfigDTO;
import com.hongshu.web.domain.entity.WebCategory;
import com.hongshu.web.domain.entity.WebLikeOrCollection;
import com.hongshu.web.domain.entity.WebNote;
import com.hongshu.web.domain.entity.WebUser;
import com.hongshu.web.mapper.web.WebCategoryMapper;
import com.hongshu.web.mapper.web.WebLikeOrCollectionMapper;
import com.hongshu.web.mapper.web.WebNoteMapper;
import com.hongshu.web.mapper.web.WebUserMapper;
import com.hongshu.web.service.IRecommendNoteService;
import com.hongshu.web.service.sys.ISysSystemConfigService;
import com.hongshu.web.service.web.IWebEsNoteService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * ES
 *
 * @author: hongshu
 */
@Service
@Slf4j
public class WebEsNoteServiceImpl extends ServiceImpl<WebNoteMapper, WebNote> implements IWebEsNoteService {

    @Autowired
    private WebUserMapper userMapper;
    @Autowired
    private WebCategoryMapper categoryMapper;
    @Autowired
    private WebNoteMapper noteMapper;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private IRecommendNoteService recommendService;
    @Autowired
    private WebLikeOrCollectionMapper likeOrCollectionMapper;
    @Autowired
    private ISysSystemConfigService systemConfigService;


    /**
     * 搜索对应的笔记
     *
     * @param currentPage 当前页
     * @param pageSize    分页数
     * @param esNoteDTO   笔记
     */
    @Override
    public Page<NoteSearchVo> getNoteByDTO(long currentPage, long pageSize, EsNoteDTO esNoteDTO) {
        Page<NoteSearchVo> page = new Page<>();

        SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
        String queryPrimary = systemConfig.getQueryPrimary();

        // ES检索
        if ("es".equals(queryPrimary)) {
            return getNoteByDTOFromES(currentPage, pageSize, esNoteDTO);
        }
        // MySQL检索
        else if ("mysql".equals(queryPrimary)) {
            return getNoteByDTOFromMysql(currentPage, pageSize, esNoteDTO);
        }

        return page;
//        Page<NoteSearchVo> page = new Page<>();
//        List<NoteSearchVo> noteSearchVoList = new ArrayList<>();
//        try {
//            SearchRequest.Builder builder = new SearchRequest.Builder().index(NoteConstant.NOTE_INDEX);
//            // 构建bool查询
//            BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
//            // 1. type条件
//            if (esNoteDTO.getType() != null && esNoteDTO.getType() != 0) {
//                // 只在type=1或2时加must
//                boolBuilder.must(m -> m.match(f -> f.field("noteType").query(esNoteDTO.getType())));
//            }
//            // 2. keyword相关should
//            if (StringUtils.isNotBlank(esNoteDTO.getKeyword())) {
//                boolBuilder.must(m -> m.bool(bb -> bb
//                        .should(h -> h.match(f -> f.field("title").boost(1f).query(esNoteDTO.getKeyword())))
//                        .should(h -> h.match(f -> f.field("username").boost(0.5f).query(esNoteDTO.getKeyword())))
//                        .should(h -> h.match(f -> f.field("content").boost(1f).query(esNoteDTO.getKeyword())))
//                        .should(h -> h.match(f -> f.field("tags").boost(4f).query(esNoteDTO.getKeyword())))
//                        .should(h -> h.match(f -> f.field("categoryName").boost(2f).query(esNoteDTO.getKeyword())))
//                        .should(h -> h.match(f -> f.field("categoryParentName").boost(1.5f).query(esNoteDTO.getKeyword())))
//                ));
//            }
//            // 3. 分类条件
//            if (StringUtils.isNotBlank(esNoteDTO.getCpid()) && StringUtils.isNotBlank(esNoteDTO.getCid())) {
//                boolBuilder.must(h -> h.match(m -> m.field("cid").query(esNoteDTO.getCid())));
//                boolBuilder.must(h -> h.match(m -> m.field("cpid").query(esNoteDTO.getCpid())));
//            } else if (StringUtils.isNotBlank(esNoteDTO.getCpid())) {
//                boolBuilder.must(h -> h.match(m -> m.field("cpid").query(esNoteDTO.getCpid())));
//            }
//            // 设置查询
//            builder.query(q -> q.bool(boolBuilder.build()));
//            // 排序
//            if (esNoteDTO.getType() != null && esNoteDTO.getType() == 1) {
//                builder.sort(o -> o.field(f -> f.field("likeCount").order(SortOrder.Desc)));
//            } else if (esNoteDTO.getType() != null && esNoteDTO.getType() == 2) {
//                builder.sort(o -> o.field(f -> f.field("time").order(SortOrder.Desc)));
//            } else {
//                // 默认排序
//                builder.sort(o -> o.field(f -> f.field("time").order(SortOrder.Desc)));
//            }
//            builder.from((int) (currentPage - 1) * (int) pageSize);
//            builder.size((int) pageSize);
//
//            SearchRequest searchRequest = builder.build();
//            SearchResponse<NoteSearchVo> searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
//            TotalHits totalHits = searchResponse.hits().total();
//            page.setTotal(Objects.requireNonNull(totalHits).value());
//            page.setCountId(esNoteDTO.getCpid());
//            List<Hit<NoteSearchVo>> hits = searchResponse.hits().hits();
//            for (Hit<NoteSearchVo> hit : hits) {
//                NoteSearchVo noteSearchVo = hit.source();
//                noteSearchVoList.add(noteSearchVo);
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new HongshuException("es查找数据异常");
//        }
//        page.setRecords(noteSearchVoList);
//        return page;
    }

    /**
     * 从ES搜索笔记
     */
    private Page<NoteSearchVo> getNoteByDTOFromES(long currentPage, long pageSize, EsNoteDTO esNoteDTO) {
        Page<NoteSearchVo> page = new Page<>();
        List<NoteSearchVo> noteSearchVoList = new ArrayList<>();

        try {
            SearchRequest.Builder builder = new SearchRequest.Builder().index(NoteConstant.NOTE_INDEX);
            // 构建bool查询
            BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
            // 1. type条件
            if (esNoteDTO.getType() != null && esNoteDTO.getType() != 0) {
                // 只在type=1或2时加must
                boolBuilder.must(m -> m.match(f -> f.field("noteType").query(esNoteDTO.getType())));
            }
            // 2. keyword相关should
            if (StringUtils.isNotBlank(esNoteDTO.getKeyword())) {
                boolBuilder.must(m -> m.bool(bb -> bb
                        .should(h -> h.match(f -> f.field("title").boost(1f).query(esNoteDTO.getKeyword())))
                        .should(h -> h.match(f -> f.field("username").boost(0.5f).query(esNoteDTO.getKeyword())))
                        .should(h -> h.match(f -> f.field("content").boost(1f).query(esNoteDTO.getKeyword())))
                        .should(h -> h.match(f -> f.field("tags").boost(4f).query(esNoteDTO.getKeyword())))
                        .should(h -> h.match(f -> f.field("categoryName").boost(2f).query(esNoteDTO.getKeyword())))
                        .should(h -> h.match(f -> f.field("categoryParentName").boost(1.5f).query(esNoteDTO.getKeyword())))
                ));
            }
            // 3. 分类条件
            if (StringUtils.isNotBlank(esNoteDTO.getCpid()) && StringUtils.isNotBlank(esNoteDTO.getCid())) {
                boolBuilder.must(h -> h.match(m -> m.field("cid").query(esNoteDTO.getCid())));
                boolBuilder.must(h -> h.match(m -> m.field("cpid").query(esNoteDTO.getCpid())));
            } else if (StringUtils.isNotBlank(esNoteDTO.getCpid())) {
                boolBuilder.must(h -> h.match(m -> m.field("cpid").query(esNoteDTO.getCpid())));
            }
            // 设置查询
            builder.query(q -> q.bool(boolBuilder.build()));
            // 排序
            if (esNoteDTO.getType() != null && esNoteDTO.getType() == 1) {
                builder.sort(o -> o.field(f -> f.field("likeCount").order(SortOrder.Desc)));
            } else if (esNoteDTO.getType() != null && esNoteDTO.getType() == 2) {
                builder.sort(o -> o.field(f -> f.field("time").order(SortOrder.Desc)));
            } else {
                // 默认排序
                builder.sort(o -> o.field(f -> f.field("time").order(SortOrder.Desc)));
            }
            builder.from((int) (currentPage - 1) * (int) pageSize);
            builder.size((int) pageSize);

            SearchRequest searchRequest = builder.build();
            SearchResponse<NoteSearchVo> searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
            TotalHits totalHits = searchResponse.hits().total();
            page.setTotal(Objects.requireNonNull(totalHits).value());
            page.setCountId(esNoteDTO.getCpid());
            List<Hit<NoteSearchVo>> hits = searchResponse.hits().hits();
            for (Hit<NoteSearchVo> hit : hits) {
                NoteSearchVo noteSearchVo = hit.source();
                noteSearchVoList.add(noteSearchVo);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new HongshuException("es查找数据异常");
        }
        page.setRecords(noteSearchVoList);
        return page;
    }

    /**
     * 从MySQL搜索笔记
     */
    private Page<NoteSearchVo> getNoteByDTOFromMysql(long currentPage, long pageSize, EsNoteDTO esNoteDTO) {
        Page<NoteSearchVo> page = new Page<>();

        // 构建查询条件
        QueryWrapper<WebNote> queryWrapper = new QueryWrapper<WebNote>()
                .eq("status", "1")
                .eq("audit_status", AuditStatusEnum.PASS.getCode());
        // 1. 笔记类型条件
        if (esNoteDTO.getType() != null && esNoteDTO.getType() != 0) {
            queryWrapper.eq("note_type", esNoteDTO.getType().toString());
        }
        // 2. 关键词搜索
        if (StringUtils.isNotBlank(esNoteDTO.getKeyword())) {
            queryWrapper.and(wrapper -> wrapper
                    .like("title", esNoteDTO.getKeyword())
                    .or().like("content", esNoteDTO.getKeyword())
                    .or().like("tags", esNoteDTO.getKeyword())
                    .or().like("author", esNoteDTO.getKeyword())
            );
        }
        // 3. 分类条件
        if (StringUtils.isNotBlank(esNoteDTO.getCpid()) && StringUtils.isNotBlank(esNoteDTO.getCid())) {
            queryWrapper.eq("cpid", esNoteDTO.getCpid())
                    .eq("cid", esNoteDTO.getCid());
        } else if (StringUtils.isNotBlank(esNoteDTO.getCpid())) {
            queryWrapper.eq("cpid", esNoteDTO.getCpid());
        }
        // 4. 排序
        if (esNoteDTO.getType() != null && esNoteDTO.getType() == 1) {
            // 图片类型按点赞数排序
            queryWrapper.orderByDesc("like_count");
        } else if (esNoteDTO.getType() != null && esNoteDTO.getType() == 2) {
            // 视频类型按时间排序
            queryWrapper.orderByDesc("time");
        } else {
            // 默认按时间排序
            queryWrapper.orderByDesc("time");
        }

        // 执行分页查询
        Page<WebNote> notePage = noteMapper.selectPage(new Page<>(currentPage, pageSize), queryWrapper);
        // 转换结果
        page.setCurrent(notePage.getCurrent());
        page.setSize(notePage.getSize());
        page.setTotal(notePage.getTotal());
        page.setCountId(esNoteDTO.getCpid());
        page.setRecords(notePage.getRecords().stream()
                .map(this::convertToNoteVo)
                .collect(Collectors.toList()));
        return page;
    }

    /**
     * 搜索对应的笔记
     *
     * @param esNoteDTO 笔记
     * @return
     */
    @Override
    public List<WebCategory> getCategoryAgg(EsNoteDTO esNoteDTO) {
        return categoryMapper.selectList(new QueryWrapper<WebCategory>().like("title", esNoteDTO.getKeyword()));
    }

    /**
     * 获取推荐笔记
     *
     * @param currentPage 当前页
     * @param pageSize    分页数
     */
    @SneakyThrows
    @Override
    public Page<NoteSearchVo> getRecommendNote(long currentPage, long pageSize) {
        Page<NoteSearchVo> page = new Page<>();

        SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
        String queryPrimary = systemConfig.getQueryPrimary();

        // ES检索
        if ("es".equals(queryPrimary)) {
            return getRecommendNoteFromES(currentPage, pageSize);
        }
        // MySQL检索
        else if ("mysql".equals(queryPrimary)) {
            return getRecommendNoteFromMysql(currentPage, pageSize);
        }

        return page;
//        Page<NoteSearchVo> page = new Page<>();
//
//        SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
//        String queryPrimary = systemConfig.getQueryPrimary();
//        // ES检索
//        if ("es".equals(queryPrimary)) {
//            String userId = AuthContextHolder.getUserId();
//            // 用户ID为空 默认随机加载100条数据
//            List<NoteSearchVo> recommendList;
//            if (StringUtils.isBlank(userId)) {
//                SearchRequest searchRequest = SearchRequest.of(s -> s
//                        .index(NoteConstant.NOTE_INDEX)
//                        .size(100));
//                SearchResponse<NoteSearchVo> searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
//                recommendList = searchResponse.hits().hits().stream()
//                        .map(Hit::source)
//                        .collect(Collectors.toList());
//                // 随机排序
//                Collections.shuffle(recommendList, new Random());
//            } else {
//                // 获取推荐列表
//                recommendList = recommendService.getRecommendNote(Long.parseLong(userId));
//            }
//            List<List<NoteSearchVo>> partition = Lists.partition(recommendList, (int) pageSize);
//            // 如果 currentPage 超出范围，返回空记录
//            if (currentPage > partition.size() || currentPage <= 0) {
//                page.setTotal(0);
//                page.setRecords(Collections.emptyList());
//                return page;
//            }
//            List<NoteSearchVo> noteSearchVos = partition.get((int) currentPage - 1);
//            page.setTotal(recommendList.size());
//            page.setRecords(noteSearchVos);
//            return page;
//        }
//        // MySQL检索
//        else if ("mysql".equals(queryPrimary)) {
//            String userId = AuthContextHolder.getUserId();
//            if (StringUtils.isBlank(userId)) {
//                // 用户未登录，随机获取100条公开笔记
//                List<NoteSearchVo> randomNotes = noteMapper.selectList(
//                                new QueryWrapper<WebNote>()
//                                        .eq("audit_status", AuditStatusEnum.PASS.getCode())
//                                        .orderBy(true, false, "RAND()")
//                                        .last("LIMIT " + 100)
//                        ).stream()
//                        .map(this::convertToNoteVo)
//                        .collect(Collectors.toList());
//                // 随机排序
//                Collections.shuffle(randomNotes, new Random());
//                // 分页处理
//                List<List<NoteSearchVo>> partition = Lists.partition(randomNotes, (int) pageSize);
//                if (currentPage > partition.size() || currentPage <= 0) {
//                    page.setTotal(0);
//                    page.setRecords(Collections.emptyList());
//                    return page;
//                }
//                List<NoteSearchVo> noteSearchVos = partition.get((int) currentPage - 1);
//                page.setTotal(randomNotes.size());
//                page.setRecords(noteSearchVos);
//            } else {
//                // 用户已登录，通过推荐算法获取推荐列表
//                page = recommendService.getRecommendNoteFromMysql(userId, currentPage, pageSize);
//            }
//            return page;
//        }
//        return page;
    }

    /**
     * 从ES获取推荐笔记
     */
    private Page<NoteSearchVo> getRecommendNoteFromES(long currentPage, long pageSize) {
        String userId = AuthContextHolder.getUserId();
        List<NoteSearchVo> recommendList;

        if (StringUtils.isBlank(userId)) {
            // 用户未登录，随机获取100条数据
            recommendList = getRandomNotesFromES(100);
        } else {
            // 用户已登录，通过推荐算法获取推荐列表
            recommendList = recommendService.getRecommendNote(Long.parseLong(userId));
        }

        return buildPageFromList(recommendList, currentPage, pageSize);
    }

    /**
     * 从MySQL获取推荐笔记
     */
    private Page<NoteSearchVo> getRecommendNoteFromMysql(long currentPage, long pageSize) {
        String userId = AuthContextHolder.getUserId();

        if (StringUtils.isBlank(userId)) {
            // 用户未登录，随机获取100条公开笔记
            List<NoteSearchVo> randomNotes = getRandomNotesFromMysql(100);
            return buildPageFromList(randomNotes, currentPage, pageSize);
        } else {
            // 用户已登录，通过推荐算法获取推荐列表
            return recommendService.getRecommendNoteFromMysql(userId, currentPage, pageSize);
        }
    }

    /**
     * 从ES随机获取指定数量的笔记
     */
    private List<NoteSearchVo> getRandomNotesFromES(int limit) {
        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index(NoteConstant.NOTE_INDEX)
                .size(limit));
        SearchResponse<NoteSearchVo> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<NoteSearchVo> recommendList = searchResponse.hits().hits().stream()
                .map(Hit::source)
                .collect(Collectors.toList());
        // 随机排序
        Collections.shuffle(recommendList, new Random());
        return recommendList;
    }

    /**
     * 从MySQL随机获取指定数量的公开笔记
     */
    private List<NoteSearchVo> getRandomNotesFromMysql(int limit) {
        List<NoteSearchVo> randomNotes = noteMapper.selectList(
                        new QueryWrapper<WebNote>()
                                .eq("audit_status", AuditStatusEnum.PASS.getCode())
                                .orderBy(true, false, "RAND()")
                                .last("LIMIT " + limit)
                ).stream()
                .map(this::convertToNoteVo)
                .collect(Collectors.toList());
        // 随机排序
        Collections.shuffle(randomNotes, new Random());
        return randomNotes;
    }

    /**
     * 从列表构建分页结果
     */
    private Page<NoteSearchVo> buildPageFromList(List<NoteSearchVo> noteList, long currentPage, long pageSize) {
        Page<NoteSearchVo> page = new Page<>();
        List<List<NoteSearchVo>> partition = Lists.partition(noteList, (int) pageSize);

        // 如果 currentPage 超出范围，返回空记录
        if (currentPage > partition.size() || currentPage <= 0) {
            page.setTotal(0);
            page.setRecords(Collections.emptyList());
            return page;
        }

        List<NoteSearchVo> noteSearchVos = partition.get((int) currentPage - 1);
        page.setTotal(noteList.size());
        page.setRecords(noteSearchVos);
        return page;
    }

    private NoteSearchVo convertToNoteVo(WebNote note) {
        NoteSearchVo vo = new NoteSearchVo();
        BeanUtils.copyProperties(note, vo);
        return vo;
    }

    /**
     * 获取推荐用户
     *
     * @param currentPage 当前页
     * @param pageSize    分页数
     */
    @SneakyThrows
    @Override
    public Page<WebUser> getRecommendUser(long currentPage, long pageSize) {
        Page<WebUser> page = new Page<>();
//        String userId = WebUtils.getRequestHeader(UserConstant.USER_ID);
        String userId = AuthContextHolder.getUserId();
        // 用户ID为空 默认随机加载100条数据
        List<WebUser> recommendList;
        if (StringUtils.isBlank(userId)) {
            SearchRequest searchRequest = SearchRequest.of(s -> s
                    .index(NoteConstant.NOTE_INDEX)
                    .size(100));
            SearchResponse<WebUser> searchResponse = elasticsearchClient.search(searchRequest, WebUser.class);
            recommendList = searchResponse.hits().hits().stream()
                    .map(Hit::source)
                    .collect(Collectors.toList());
            // 随机排序
            Collections.shuffle(recommendList, new Random());
        } else {
            // 获取推荐列表
            recommendList = recommendService.getRecommendUser(Long.parseLong(userId));
        }
        List<List<WebUser>> partition = Lists.partition(recommendList, (int) pageSize);
        // 如果 currentPage 超出范围，返回空记录
        if (currentPage > partition.size() || currentPage <= 0) {
            page.setTotal(0);
            page.setRecords(Collections.emptyList());
            return page;
        }
        List<WebUser> userList = partition.get((int) currentPage - 1);
        page.setTotal(recommendList.size());
        page.setRecords(userList);
        return page;
    }

    /**
     * 获取热榜笔记
     *
     * @param currentPage 当前页
     * @param pageSize    分页数
     */
    @Override
    public Page<NoteSearchVo> getHotNote(long currentPage, long pageSize) {
        Page<NoteSearchVo> page = new Page<>(currentPage, pageSize);
        List<NoteSearchVo> noteSearchVoList = new ArrayList<>();

        try {
            // 构建搜索请求
            SearchRequest.Builder builder = new SearchRequest.Builder()
                    .index(NoteConstant.NOTE_INDEX)
                    .from(Math.toIntExact((currentPage - 1) * pageSize)) // 设置分页起始点
                    .size(Math.toIntExact(pageSize)) // 设置分页大小
                    .sort(s -> s.field(f -> f.field("likeCount").order(SortOrder.Desc))); // 按 likeCount 降序排序

            SearchRequest searchRequest = builder.build();

            // 执行搜索请求
            SearchResponse<NoteSearchVo> searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
            TotalHits totalHits = searchResponse.hits().total();

            // 获取搜索结果
            List<Hit<NoteSearchVo>> hits = searchResponse.hits().hits();
            if (CollectionUtil.isNotEmpty(hits)) {
                for (Hit<NoteSearchVo> hit : hits) {
                    NoteSearchVo noteSearchVo = hit.source();
                    noteSearchVoList.add(noteSearchVo);
                }
            }

            // 设置分页结果
            page.setTotal(totalHits != null ? totalHits.value() : 0);
            page.setRecords(noteSearchVoList);

        } catch (Exception e) {
            e.printStackTrace();
            // 这里可以进一步处理异常，比如记录日志或者抛出自定义异常
        }
        return page;
    }


    /**
     * 增加笔记
     *
     * @param noteSearchVo 笔记
     */
    @Override
    public void addNote(NoteSearchVo noteSearchVo) {
        try {
            CreateResponse createResponse = elasticsearchClient.create(e -> e.index(NoteConstant.NOTE_INDEX).id(noteSearchVo.getId()).document(noteSearchVo));
            log.info("createResponse.result{}", createResponse.result());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 修改笔记
     *
     * @param noteSearchVo 笔记
     */
    @Override
    public void updateNote(NoteSearchVo noteSearchVo) {
        try {
            UpdateResponse<NoteSearchVo> updateResponse = elasticsearchClient.update(e -> e.index(NoteConstant.NOTE_INDEX).id(noteSearchVo.getId()).doc(noteSearchVo), NoteSearchVo.class);
            log.info("updateResponse.result() = " + updateResponse.result());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除es中的笔记
     *
     * @param noteId 笔记 ID
     */
    @Override
    public void deleteNote(String noteId) {
        try {
            DeleteResponse deleteResponse = elasticsearchClient.delete(e -> e.index(NoteConstant.NOTE_INDEX).id(String.valueOf(noteId)));
            log.info("deleteResponse.result() ={} ", deleteResponse.result());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 批量增加笔记
     */
    @Override
    public void addNoteBulkData() {
        List<WebNote> noteList = noteMapper.selectList(new QueryWrapper<WebNote>().eq("audit_status", 1));
        List<NoteSearchVo> noteSearchVoList = DozerUtil.convertor(noteList, NoteSearchVo.class);
        for (NoteSearchVo noteSearchVo : noteSearchVoList) {
            WebUser user = userMapper.selectOne(new QueryWrapper<WebUser>().eq("id", noteSearchVo.getUid()));
            if (user != null) {
                noteSearchVo.setAvatar(user.getAvatar());
                noteSearchVo.setUsername(user.getUsername());

                // 是否点赞
                List<WebLikeOrCollection> likeOrCollections = likeOrCollectionMapper.selectList(new QueryWrapper<WebLikeOrCollection>().eq("uid", user.getId()).eq("type", 1));
                List<String> likeOrCollectionIds = likeOrCollections.stream().map(WebLikeOrCollection::getLikeOrCollectionId).collect(Collectors.toList());
                noteSearchVo.setIsLike(likeOrCollectionIds.contains(noteSearchVo.getId()));
            } else {
                noteSearchVo.setAvatar(null);
                noteSearchVo.setUsername(null);
                noteSearchVo.setIsLike(false);
            }
        }
        try {
            List<BulkOperation> result = new ArrayList<>();
            for (NoteSearchVo noteSearchVo : noteSearchVoList) {
                result.add(new BulkOperation.Builder().create(
                        d -> d.document(noteSearchVo).id(noteSearchVo.getId()).index(NoteConstant.NOTE_INDEX)).build());
            }
            BulkResponse bulkResponse = elasticsearchClient.bulk(e -> e.index(NoteConstant.NOTE_INDEX).operations(result));
            log.info("createResponse.result{}", bulkResponse.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清空笔记
     */
    @Override
    public void delNoteBulkData() {
        try {
            // 检查索引是否存在
            if (elasticsearchClient.indices().exists(existsRequest -> existsRequest.index(NoteConstant.NOTE_INDEX)).value()) {
                // 删除索引
                DeleteIndexRequest deleteIndexRequest = DeleteIndexRequest.of(builder -> builder.index(NoteConstant.NOTE_INDEX));
                elasticsearchClient.indices().delete(deleteIndexRequest);
                log.info("成功删除索引: {}", NoteConstant.NOTE_INDEX);
            } else {
                log.info("索引 {} 不存在，无需删除", NoteConstant.NOTE_INDEX);
            }
            // 重新创建索引
            CreateIndexRequest createIndexRequest = CreateIndexRequest.of(builder -> builder.index(NoteConstant.NOTE_INDEX));
            elasticsearchClient.indices().create(createIndexRequest);
            log.info("成功创建索引: {}", NoteConstant.NOTE_INDEX);
        } catch (Exception e) {
            log.error("Error occurred while deleting and recreating index: {}", NoteConstant.NOTE_INDEX, e);
        }
    }

    /**
     * 重置
     */
    @Override
    public void refreshNoteData() {
        this.delNoteBulkData();
        this.addNoteBulkData();
    }

    /**
     * 查询附近数据
     *
     * @param currentPage
     * @param pageSize
     * @return
     */
    public NearbyNotesPage<NoteSearchVo> getNotesNearBy(long currentPage, long pageSize) {
        NearbyNotesPage<NoteSearchVo> page = new NearbyNotesPage<>((int) currentPage, (int) pageSize);

        String cityName = this.getCurrentCity();
        log.info("获取到的城市名称: {}", cityName);
        page.setCityName(cityName != null ? cityName : "附近");

        if (StringUtils.isNotBlank(cityName)) {
            // 调用Mapper方法，传入审核状态参数
            Page<NoteSearchVo> notePage = noteMapper.selectNotesWithUserInfo(
                    new Page<>((int) currentPage, (int) pageSize),
                    cityName,
                    AuditStatusEnum.PASS.getCode()  // 传入审核状态
            );

            page.setTotal(notePage.getTotal());
            page.setRecords(notePage.getRecords());
        }
        return page;
    }

    @Autowired
    private RedisUtils redisUtils;
    private static final String IP_CITY_CACHE_PREFIX = "ip_city:" ;
    private static final long CACHE_EXPIRE_HOURS = 24;

    private String getCurrentCity() {
        try {
            String ip = IpUtils.getIpAddr();
            log.info("获取到的ip: {}", ip);

            // 添加空IP检查
            if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                log.warn("获取到无效IP: {}", ip);
                return null;
            }

            // 先从缓存查询
            String cacheKey = IP_CITY_CACHE_PREFIX + ip;
            String cachedCity = redisUtils.get(cacheKey);
            if (StringUtils.isNotBlank(cachedCity)) {
                log.info("从缓存获取城市信息: {}", cachedCity);
                return cachedCity;
            }

            String address = AddressUtils.getRealAddressByIP(ip);
            log.info("获取到的地址: {}", address);

            String city = this.extractCity(address);

            // 缓存结果
            if (StringUtils.isNotBlank(city)) {
                redisUtils.set(cacheKey, city, CACHE_EXPIRE_HOURS * 3600); // 转换为秒
                log.info("城市信息已缓存: {}", city);
            }

            return city;
        } catch (Exception e) {
            log.error("获取当前位置失败", e);
            return null;
        }
    }

    private String extractCity(String fullAddress) {
        if (StringUtils.isBlank(fullAddress)) {
            return null;
        }
        fullAddress = fullAddress.trim();
        // 直辖市直接返回
        String[] directCities = {"北京", "上海", "天津", "重庆"};
        for (String city : directCities) {
            if (fullAddress.contains(city)) {
                return city;
            }
        }
        // 使用更精确的正则匹配
        Pattern pattern = Pattern.compile("([^省]+省)?([^市]+市)");
        Matcher matcher = pattern.matcher(fullAddress);
        if (matcher.find()) {
            String city = matcher.group(2);
            if (city != null) {
                // 统一处理：trim + 去掉"市"字 + 再次trim
                return city.trim().replace("市", "").trim();
            }
        }
        return null;
    }

}
